#include "movescuthread.h"
#include "../share/configfiles.h"
#include "../share/global.h"
#include "../MainStation/mainwindow.h"

#include "dcmtk/config/osconfig.h"
#include "dcmtk/ofstd/ofstd.h"
#include "dcmtk/dcmnet/dicom.h"
#include "dcmtk/dcmnet/dimse.h"
#include "dcmtk/dcmnet/diutil.h"
#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmdata/dcuid.h"
#include "dcmtk/dcmdata/dcdict.h"
#include "dcmtk/dcmdata/dcdeftag.h"
#include "dcmtk/dcmdata/dcmetinf.h"
#include "dcmtk/dcmdata/dcuid.h"      /* for dcmtk version name */
#include "dcmtk/dcmdata/dcdicent.h"
#include "dcmtk/dcmdata/dcostrmz.h"   /* for dcmZlibCompressionLevel */
#include "dcmtk/dcmnet/dfindscu.h"

typedef struct {
    T_ASC_Association *assoc;
    T_ASC_PresentationContextID presId;
} MyCallbackInfo;

static void
moveCallback(void *callbackData, T_DIMSE_C_MoveRQ *request,
    int responseCount, T_DIMSE_C_MoveRSP *response)
{
    OFCondition cond = EC_Normal;
    MyCallbackInfo *myCallbackData;

    myCallbackData = OFstatic_cast(MyCallbackInfo*, callbackData);

    /*
    OFString temp_str;
    OFLOG_INFO(movescuLogger, "Move Response " << responseCount << ":" << OFendl << DIMSE_dumpMessage(temp_str, *response, DIMSE_INCOMING));

    /* should we send a cancel back ??
    if (opt_cancelAfterNResponses == responseCount) {
        OFLOG_INFO(movescuLogger, "Sending Cancel Request: MsgID " << request->MessageID
                 << ", PresID " << myCallbackData->presId);
        cond = DIMSE_sendCancelRequest(myCallbackData->assoc,
            myCallbackData->presId, request->MessageID);
        if (cond != EC_Normal) {
            OFLOG_ERROR(movescuLogger, "Cancel Request Failed: " << DimseCondition::dump(temp_str, cond));
        }
    }
    */
}


static OFCondition
addPresentationContext(T_ASC_Parameters *params,
                        T_ASC_PresentationContextID pid,
                        const char* abstractSyntax)
{
    /*
    ** We prefer to use Explicitly encoded transfer syntaxes.
    ** If we are running on a Little Endian machine we prefer
    ** LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
    ** Some SCP implementations will just select the first transfer
    ** syntax they support (this is not part of the standard) so
    ** organise the proposed transfer syntaxes to take advantage
    ** of such behaviour.
    **
    ** The presentation contexts proposed here are only used for
    ** C-FIND and C-MOVE, so there is no need to support compressed
    ** transmission.
    */

    const char* transferSyntaxes[] = { NULL, NULL, NULL };
    int numTransferSyntaxes = 0;

    /* We prefer explicit transfer syntaxes.
     * If we are running on a Little Endian machine we prefer
     * LittleEndianExplicitTransferSyntax to BigEndianTransferSyntax.
     */
    if (gLocalByteOrder == EBO_LittleEndian)  /* defined in dcxfer.h */
    {
        transferSyntaxes[0] = UID_LittleEndianExplicitTransferSyntax;
        transferSyntaxes[1] = UID_BigEndianExplicitTransferSyntax;
    } else {
        transferSyntaxes[0] = UID_BigEndianExplicitTransferSyntax;
        transferSyntaxes[1] = UID_LittleEndianExplicitTransferSyntax;
    }
    transferSyntaxes[2] = UID_LittleEndianImplicitTransferSyntax;
    numTransferSyntaxes = 3;

    return ASC_addPresentationContext(
        params, pid, abstractSyntax,
        transferSyntaxes, numTransferSyntaxes);
}

void MoveScuThread::run()
{
#ifdef HAVE_WINSOCK_H
    WSAData winSockData;
    WORD winSockVersionNeeded = MAKEWORD( 1, 1 );
    WSAStartup(winSockVersionNeeded, &winSockData);
#endif
    T_ASC_Parameters *params = NULL;
    DIC_NODENAME localHost;
    DIC_NODENAME peerHost;
    T_ASC_Network *net = NULL;
    T_ASC_Association *assoc = NULL;
    OFString temp_str;

    emit progressMsg(tr("Moving..."));

    OFCondition cond = ASC_initializeNetwork(NET_REQUESTOR, 0, 30, &net);

    if (EC_Normal == cond) {
        /* set up main association */
        cond = ASC_createAssociationParameters(&params, ASC_DEFAULTMAXPDU);
    } else {
        DimseCondition::dump(temp_str, cond);
        emit progressMsg(tr("Error: %1.").arg(temp_str.c_str()));
    }

    if (EC_Normal == cond) {
        QString aeTitle = mainWindow->getStationInfo().aetitle;
        if (aeTitle.isEmpty()) aeTitle = DEFAULT_STATION_AET;
        ASC_setAPTitles(params, aeTitle.toLocal8Bit().data(), qrscp.aetitle.toLocal8Bit().data(), NULL);

        gethostname(localHost, sizeof(localHost) - 1);
        sprintf(peerHost, "%s:%d", qrscp.hostname.toLocal8Bit().data(), qrscp.port);
        ASC_setPresentationAddresses(params, localHost, peerHost);

        /*
        * We also add a presentation context for the corresponding
        * find sop class.
        */
       cond = addPresentationContext(params, 1, UID_FINDStudyRootQueryRetrieveInformationModel);
       cond = addPresentationContext(params, 3, UID_MOVEStudyRootQueryRetrieveInformationModel);
    } else {
        DimseCondition::dump(temp_str, cond);
        emit progressMsg(tr("Error: %1.").arg(temp_str.c_str()));
    }

    if (EC_Normal == cond) {
        cond = ASC_requestAssociation(net, params, &assoc);
        if (ASC_countAcceptedPresentationContexts(params) == 0)
            cond = EC_IllegalParameter;
    } else {
        DimseCondition::dump(temp_str, cond);
        emit progressMsg(tr("Error: %1.").arg(temp_str.c_str()));
    }

    if (EC_Normal == cond) {
        DcmDataset *overrideKeys = new DcmDataset;
        overrideKeys->putAndInsertString(DCM_QueryRetrieveLevel, "STUDY");

        if (!patientId.isEmpty()) {
            overrideKeys->putAndInsertString(DCM_PatientID, patientId.toLocal8Bit().data());
        }
        if (!patientName.isEmpty()) {
            overrideKeys->putAndInsertString(DCM_PatientName, patientName.toLocal8Bit().data());
        }
        if (!accNumber.isEmpty()) {
            overrideKeys->putAndInsertString(DCM_AccessionNumber, accNumber.toLocal8Bit().data());
        }
        if (!modality.isEmpty()) {
            overrideKeys->putAndInsertString(DCM_Modality, modality.toLatin1().data());
        }
        if (fromTime.isValid() || toTime.isValid()) {
            QString date, time;
            if (fromTime.isValid()) {
                date += fromTime.date().toString("yyyyMMdd");
                time += fromTime.time().toString("hhmmss");
            }
            date.append("-");
            time.append("-");
            if (toTime.isValid()) {
                date += toTime.date().toString("yyyyMMdd");
                time += toTime.time().toString("hhmmss");
            }

            overrideKeys->putAndInsertString(DCM_StudyDate, date.toLatin1().data());
            overrideKeys->putAndInsertString(DCM_StudyTime, date.toLatin1().data());
        }
        if (!modality.isEmpty()) {
            overrideKeys->putAndInsertString(DCM_Modality, modality.toLocal8Bit().data());
        }

        foreach (const QString &studyUid, studyUidList) {
            overrideKeys->putAndInsertString(DCM_StudyInstanceUID, studyUid.toLatin1().data());
            cond = moveSCU(assoc, overrideKeys);
        }

        delete overrideKeys;
    }


    /* tear down association */
    if (cond == EC_Normal) {
        cond = ASC_releaseAssociation(assoc);
        emit progressMsg(tr("Finding ended."));
    } else {
        DimseCondition::dump(temp_str, cond);
        emit progressMsg(tr("Error: %1.").arg(temp_str.c_str()));
        cond = ASC_abortAssociation(assoc);
    }

    ASC_destroyAssociation(&assoc);
    ASC_dropNetwork(&net);

    emit progressMsg(tr("Moving Finished."));

#ifdef HAVE_WINSOCK_H
    WSACleanup();
#endif
}

OFCondition MoveScuThread::moveSCU(T_ASC_Association *assoc, DcmDataset *keys)
{
    T_ASC_PresentationContextID presId;
    T_DIMSE_C_MoveRQ    req;
    T_DIMSE_C_MoveRSP   rsp;
    DIC_US              msgId = assoc->nextMsgID++;
    DcmDataset          *rspIds = NULL;
    DcmDataset          *statusDetail = NULL;
    MyCallbackInfo      callbackData;

    /* which presentation context should be used */
    presId = ASC_findAcceptedPresentationContextID(assoc, UID_MOVEStudyRootQueryRetrieveInformationModel);
    if (presId == 0) return DIMSE_NOVALIDPRESENTATIONCONTEXTID;

    callbackData.assoc = assoc;
    callbackData.presId = presId;

    req.MessageID = msgId;
    strcpy(req.AffectedSOPClassUID, UID_MOVEStudyRootQueryRetrieveInformationModel);
    req.Priority = DIMSE_PRIORITY_MEDIUM;
    req.DataSetType = DIMSE_DATASET_PRESENT;
    strcpy(req.MoveDestination, mainWindow->getStationInfo().aetitle.toLocal8Bit().data());

    OFCondition cond = DIMSE_moveUser(assoc, presId, &req, keys,
        moveCallback, &callbackData, DIMSE_BLOCKING, 0, 0, 0,
        NULL, &rsp, &statusDetail, &rspIds, true);

    if (cond == EC_Normal) {
        OFString temp_str;
        DIMSE_dumpMessage(temp_str, rsp, DIMSE_INCOMING);
        emit progressMsg(QString("%1").arg(temp_str.c_str()));
    } else {
        OFString temp_str;
        DimseCondition::dump(temp_str, cond);
        emit progressMsg(tr("Move Request Failed: %1.").arg(temp_str.c_str()));
    }
    if (statusDetail != NULL) {
        delete statusDetail;
    }

    if (rspIds != NULL) delete rspIds;

    return cond;
}
